﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using MvcExtensions.Attributes;

namespace MvcExtensions.Extensions
{
    public static class ControlExtensions
    {
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            object htmlAttributes = null)
        {
            var selectListItems = GetSelectListItems(htmlHelper, expression);

            return SelectExtensions.DropDownListFor(htmlHelper, expression, selectListItems, htmlAttributes);
        }

        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            IDictionary<string, object> htmlAttributes)
        {
            var selectListItems = GetSelectListItems(htmlHelper, expression);

            return SelectExtensions.DropDownListFor(htmlHelper, expression, selectListItems, htmlAttributes);
        }

        public static MvcHtmlString MergeTable<TModel>(this HtmlHelper<TModel> helper,
                                                                  MvcHtmlString partialResult1,
                                                                  MvcHtmlString partialResult2)
        {
            if (partialResult1 == null && partialResult2 == null)
                return MvcHtmlString.Empty;

            if (partialResult1 == null)
                return partialResult2;
            if (partialResult2 == null)
                return partialResult1;

            var result1 = partialResult1.ToString();
            var lastClosingtable = result1.LastIndexOf("</table>", StringComparison.OrdinalIgnoreCase);

            var result2 = partialResult2.ToString();
            var firstOpeningtable = result2.LastIndexOf("<table", StringComparison.OrdinalIgnoreCase);

            if (lastClosingtable >= 0 && firstOpeningtable >= 0)
            {
                result1 = result1.Substring(0, lastClosingtable); //no need any things after </table>
                result2 = result2.Substring(firstOpeningtable + 7); // remove <table>
            }

            return MvcHtmlString.Create(result1 + result2);
        }

        public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
            Expression<Func<TModel, TProperty>> expression, string partialViewName)
        {
            var name = ExpressionHelper.GetExpressionText(expression);
            var model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
            var viewData = new ViewDataDictionary(helper.ViewData)
            {
                TemplateInfo = new TemplateInfo
                {
                    HtmlFieldPrefix = name
                }
            };

            return helper.Partial(partialViewName, model, viewData);
        }

        public static MvcHtmlString PartialFor<TModel>(this HtmlHelper<TModel> helper, string partialViewName, string htmlFieldPrefix)
        {
            var viewData = new ViewDataDictionary(helper.ViewData)
            {
                TemplateInfo = new TemplateInfo
                {
                    HtmlFieldPrefix = htmlFieldPrefix
                }
            };

            return helper.Partial(partialViewName, helper.ViewData.Model, viewData);
        }


        public static MvcHtmlString Partial<TModel>(this HtmlHelper<TModel> helper, string partialViewName, object model, string htmlFieldPrefix)
        {
            var viewData = new ViewDataDictionary
            {
                TemplateInfo = new TemplateInfo
                {
                    HtmlFieldPrefix = htmlFieldPrefix
                }
            };

            return helper.Partial(partialViewName, model, viewData);
        }

        public static MvcHtmlString SodaCheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
                                                          Expression<Func<TModel, bool>> expression,
                                                          object checkboxAttributes = null,
                                                          object labelAttributes = null)
        {
            var attributeList = checkboxAttributes != null
                                    ? HtmlHelper.AnonymousObjectToHtmlAttributes(checkboxAttributes)
                                    : new RouteValueDictionary();

            var htmlString = htmlHelper.CheckBoxFor(expression, attributeList);

            var displayName = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).DisplayName;
            var lblAttributes = string.Empty;
            if (labelAttributes != null)
            {
                lblAttributes = KnockoutControlExtensions.ConvertDictionaryToAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(labelAttributes));
            }

            return MvcHtmlString.Create(string.Format("<{0} {3}>{1}{2}</{0}>", "label"
                                                      , htmlString, displayName,
                                                      lblAttributes));
        }

        public static RouteValueDictionary GetAttributeList(object htmlAttributes)
        {
            return htmlAttributes != null
                       ? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)
                       : new RouteValueDictionary();
        }

        public static string ConvertDictionaryToAttributes(object htmlAttributes)
        {
            return ConvertDictionaryToAttributes(GetAttributeList(htmlAttributes));
        }

        public static string ConvertDictionaryToAttributes(RouteValueDictionary dictionary)
        {
            var attributes = dictionary.Any() ? " " : string.Empty;
            return dictionary.Aggregate(attributes, (c, att) => c + string.Format("{0}='{1}'", att.Key, att.Value));
        }

        public static MvcHtmlString SodaRadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                          Expression<Func<TModel, TProperty>> expression,
                                                                          object htmlAttributes = null,
                                                                          object groupHtmlAttributes = null)
        {
            var attributeList = GetAttributeList(htmlAttributes);

            var selectListItems = GetSelectListItems(htmlHelper, expression).ToList();

            var groupHtmlAttributeList = GetAttributeList(groupHtmlAttributes);

            var groupHtmlAttributeString = ConvertDictionaryToAttributes(groupHtmlAttributeList);

            var htmlStringbuilder = new StringBuilder(1024);
            htmlStringbuilder.AppendFormat("<div{0}>", groupHtmlAttributeString);

            //select first if none of are selected.
            if (selectListItems.Any() && !selectListItems.Any(x => x.Selected))
            {
                selectListItems.First().Selected = true;
            }

            foreach (var item in selectListItems)
            {
                var attributes = new RouteValueDictionary(attributeList);
                if (item.Selected)
                {
                    attributes.Add("checked", "checked");
                }
                var radioButtonString = InputExtensions.RadioButtonFor(htmlHelper,
                                                                       expression,
                                                                       item.Value,
                                                                       attributes);
                htmlStringbuilder.AppendFormat("<div><label>{0}{1}</label></div>", radioButtonString, item.Text);
            }

            htmlStringbuilder.Append("</div>");

            return MvcHtmlString.Create(htmlStringbuilder.ToString());
        }

        public static MvcHtmlString DateTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                   Expression<Func<TModel, TProperty>> expression,
                                                                   object htmlAttributes = null)
        {
            var attributeList = htmlAttributes != null
                                    ? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)
                                    : new RouteValueDictionary();

            if (!attributeList.ContainsKey("class"))
            {
                attributeList.Add("class", "date");
            }

            return htmlHelper.TextBoxFor(expression, "{0:dd/MM/yyyy}", attributeList);
        }

        public static IHtmlString ActionUrl<TModel>(this HtmlHelper<TModel> htmlHelper, UrlHelper url, string actionName = null, string controllerName = null, object routeValue = null)
        {
            return MvcHtmlString.Create(url.Action(actionName, controllerName, routeValue));
        }

        public static MvcHtmlString AuthorisedActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
        {
            var httpContext = HttpContext.Current;

            //var dataProvider = new SecurityDataProvider();

            if (httpContext.User != null && httpContext.User.Identity != null)
            {
                return htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
            }
            return MvcHtmlString.Empty;
        }


        public static MvcHtmlString DisplayTextValueFor<TModel, TValue>(this HtmlHelper<TModel> html,
                                                               Expression<Func<TModel, TValue>> expression)
        {
            var propertyInfo = GetPropertyInfo(expression);
            var attributes = propertyInfo.GetCustomAttributes(typeof(ServiceLookupAttribute)).ToList();
            if (attributes.Any() || propertyInfo.PropertyType.IsEnum)
            {
                var lookuplist = GetSelectListItems(html, expression);
                var selectedLookup = lookuplist.FirstOrDefault(x => x.Selected);
                var value = selectedLookup != null ? selectedLookup.Text : string.Empty;
                return MvcHtmlString.Create(value);
            }
            return html.DisplayFor(expression);
        }

        #region static methods

        public static IEnumerable<SelectListItem> GetSelectListItems<TModel, TProperty>(HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression)
        {
            var propertyInfo = GetPropertyInfo(expression);

            var selectListItems = GetLookItems(propertyInfo);

            var propertyValue = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model;

            return selectListItems
                .Select(x => new SelectListItem
                {
                    Text = x.Description,
                    Value = x.Id,
                    Selected = propertyValue != null && (x.Id == propertyValue.ToString() || x.Key == propertyValue.ToString()),
                });
        }

        internal static PropertyInfo GetPropertyInfo<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            var memberExpression = (MemberExpression)expression.Body;
            var propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
            var containerType = memberExpression.Expression.Type;
            var propertyInfo = containerType.GetProperty(propertyName);
            return propertyInfo;
        }

        internal static IEnumerable<Lookup> GetLookItems(PropertyInfo propertyInfo)
        {
            var attributes = propertyInfo.GetCustomAttributes(typeof(ServiceLookupAttribute)).ToList();

            var selectListItems = new List<Lookup>();

            if (attributes.Any())
            {
                var serviceLookup = (ServiceLookupAttribute)attributes.First();
                var serviceObj = GetOrSetDependencyObject(serviceLookup.LookupType);
                var methodInfo = serviceLookup.LookupType.GetMethod(serviceLookup.MethodName);
                var returnObj = methodInfo.Invoke(serviceObj, null) as IEnumerable<Lookup>;

                selectListItems = returnObj.ToList();
            }
            else if (propertyInfo.PropertyType.IsEnum)
            {
                selectListItems = DataExtensions.GetEnumLookup(propertyInfo.PropertyType).ToList();
            }
            return selectListItems;
        }

        internal static string GetPropertyName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            var memberExpression = (MemberExpression)expression.Body;
            var propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
            return propertyName;
        }

        internal const string GetOrSetDependencyObjectString = "GetOrSetDependencyObject";

        internal static object GetOrSetDependencyObject(Type type)
        {
            ConcurrentDictionary<Type, object> doDictionary;
            if (!HttpContext.Current.Items.Contains(GetOrSetDependencyObjectString))
            {
                doDictionary = new ConcurrentDictionary<Type, object>();
                HttpContext.Current.Items[GetOrSetDependencyObjectString] = doDictionary;
            }
            else
            {
                doDictionary = (ConcurrentDictionary<Type, object>)HttpContext.Current.Items[GetOrSetDependencyObjectString];
            }

            return doDictionary.GetOrAdd(type, (x) => DependencyResolver.Current.GetService(x));
        }

        public static string GetModelName<TModel>(this HtmlHelper<TModel> helper)
        {
            var modelName = helper.ViewData.Model.GetType().Name;
            var prefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix;
            return string.IsNullOrEmpty(prefix) ? modelName : string.Format("{0}.{1}", modelName, prefix);
        }

        #endregion

    }

}